#ifndef QuadTree_h
#define QuadTree_h
#include "QNode.h"
#include "QRectangle.h"
#include <string>
#include <fstream>
#include <map>
#include <vector>
#include "BrickNormal.h"
#include "BrickHard.h"
#include "BrickGold.h"
#include "GroundGrass.h"
#include "Goomba.h"
#include "ScenaryObject.h"
#include "BrickRedMushroom.h"
#include "Turtle.h"
#include "TubeBody.h"
#include "TubeHead.h"
using namespace std;

class QuadTree
{
public:
	QuadTree()
	{
		_root = NULL;
	}

	~QuadTree()
	{
		if(_root != NULL)
		{
			delete _root;
			_root = NULL;
		}
	}

	//for drawing on scene
	vector<GameObject *> VectorCollisionObject;
	vector<GameObject *> VectorScenaryObject;

	//for checking collision
	vector<GameObject *> VectorCheckingColliObject;

	//Update 
	//parameter2 is screen bouding box
	void UpdateVectorObject(QNode *node, QRectangle rect, boolean forDrawing)
	{
	
		//if it's root then clear all objects
		if(node == _root) // ~ if( node -> GetNodeID() == 0 )
		{
			if(forDrawing)
			{
				VectorCollisionObject.clear();
				VectorScenaryObject.clear();
				idListDraw.clear();
			}
			else
			{
				VectorCheckingColliObject.clear();
				idListCollision.clear();
			}
		}


		//divide
		if(node -> GetChildNode(1) != NULL)
		{
			if(node -> GetChildNode(1) -> GetBound().Intersect(rect))
				UpdateVectorObject(node -> GetChildNode(1), rect, forDrawing);
			if(node -> GetChildNode(2) -> GetBound().Intersect(rect))
				UpdateVectorObject(node->GetChildNode(2), rect, forDrawing);
			if(node -> GetChildNode(3) -> GetBound().Intersect(rect))
				UpdateVectorObject(node -> GetChildNode(3), rect, forDrawing);
			if(node -> GetChildNode(4) -> GetBound().Intersect(rect))
				UpdateVectorObject(node -> GetChildNode(4), rect, forDrawing);
		}
		else 
			//if top lelf of node == NULL ( ~ is leaf)
			// & intersect
			if(node -> GetBound().Intersect(rect))
			{
				for(size_t i = 0; i < node -> GetListGameObj().size(); ++i)
				{

					//check if that game object has already been in VectorObject
					if(!Check(node -> GetListGameObj()[i] -> GetID(), forDrawing))
					{
						if(forDrawing)
						{
							if(node -> GetListGameObj()[i]->isCollisionObj())
								VectorCollisionObject.push_back(node -> GetListGameObj()[i]);
							else VectorScenaryObject.push_back(node -> GetListGameObj()[i]);
							idListDraw.push_back(node -> GetListGameObj()[i] -> GetID());
						}
						else
						{
							if(node -> GetListGameObj()[i]->isCollisionObj())
								VectorCheckingColliObject.push_back(node -> GetListGameObj()[i]);
							idListCollision.push_back(node -> GetListGameObj()[i] -> GetID());
						}
					}
				}
			}
	}

	QNode *GetRootNode()
	{
		return _root;
	}

	void SetRootNode(QNode * node)
	{
		_root = node;
	}

	void Deserialize(string fileNode)
	{
		ifstream fileStream;
		fileStream.open(fileNode, std::fstream::in);

		string line;
		int idNode, posX, posY , width, height;
		int idGameObj;

		if ( fileStream.is_open())
		{
			while ( !fileStream.eof() )
			{
				string::iterator i, j;
				vector<string> member;
				getline(fileStream,line);
				if(line != "")
				{
					for (i = line.begin(); i < line.end(); )
					{
						j = find(i,line.end(),' ');
						string temp = string(i,j );
						if(i != j)
							member.push_back(temp);
						//there must be " " at the end of each line except for the first line of node file
						i = j + 1;
					}

					//c_str: a pointer to an array that contains a null-terminated sequence of characters
					// c_str converts a string to char*
					//atoi: convert string to integer
					idNode = atoi(member[0].c_str());
					posX = atoi(member[1].c_str());
					posY = atoi(member[2].c_str());
					width = atoi(member[3].c_str());
					height = atoi(member[4].c_str());

					QNode* node = new QNode(idNode, QRectangle((float)posX, (float)posY, (float)width, (float)height));
					if(member.size() >=5)
					{
						for (size_t i = 5; i < member.size(); ++i)
						{
							idGameObj = atoi(member[i].c_str());
							//add game object to node's object list
							if(idGameObj != idMario)
								node->InsertObject(mapGameObject[idGameObj]);
						}
					}
					mapNode.insert(pair<int, QNode*>(idNode, node));
				}
			}
		}
		fileStream.close();
		//set pointers
		//if node is root -> set root node.
		//Then set "->"
		map<int , QNode*>::iterator item;
		for (item = mapNode.begin() ; item != mapNode.end(); ++item)
		{
			//detect root node
			if(item -> first == 0) 
				this -> SetRootNode(item->second);
			else 
			{
				int parentID = (item -> second) -> GetParentId(); 
				QNode* pNode = mapNode[parentID];

				//childIndex = 1 2 3 4
				int childIndex = item -> first % 8; 
				switch(childIndex)
				{
				case 1:
					pNode->_tl = item->second;
					break;
				case 2:
					pNode->_tr = item->second;
					break;
				case 3:
					pNode->_bl = item->second;
					break;
				case 4:
					pNode->_br = item->second;
					break;
				}
			}
		}
	}

	void InitGameObjectList(string fileObj)
	{
		ifstream stream;
		stream.open(fileObj,std::fstream::in);
		int id, x, y, tempType, movingWidth;
		GameObjectType type;

		if(stream.is_open())
		{
			//remove the first line (not use)
			string line;
			stream>>mapWidth>>mapHeight;
			getline(stream,line);

			while (!stream.eof())
			{
				stream >> id;
				stream >> tempType;
				stream >> x;
				stream >> y;
				stream >> movingWidth;
				type = (GameObjectType)tempType;

				InsertToMapGameObject(id, type, x, y, movingWidth);
			}
		}
		stream.close();
	}

	int GetMapWidth()
	{
		return mapWidth;
	}
	int GetMapHeight()
	{
		return mapHeight + 100;
	}

	map<int, GameObject*> GetMapGameObject()
	{
		return mapGameObject;
	}
	void SetMapGameObject(map<int, GameObject*>  a)
	{
		mapGameObject = a;
	}

private: 
	QNode *_root;

	map<int, GameObject*> mapGameObject;
	map<int, QNode*> mapNode;
	vector<int> idListDraw;
	vector<int> idListCollision;
	int mapWidth, mapHeight;
	int idMario;

	void InsertToMapGameObject(int id, GameObjectType type, int x, int y, int movingWidth)
	{
		GameObject *gobj;
		switch (type)
		{
		case MarioPlayer: 
			idMario = id;
			Mario::SetPosition(D3DXVECTOR2((float)x, (float)y));
			break;
		case BrickNormalType:
			gobj = new BrickNormal(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case BrickRedType:
			gobj = new BrickRedMushroom(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case BrickGreenType:
			break;
		case GroundGrassType:
			gobj = new GroundGrass(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case BrickGoldType:
			gobj = new BrickGold(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case BrickHardType:
			gobj = new BrickHard(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case GoombaType:
			gobj = new Goomba(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case TurtleType:
			gobj = new Turtle(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case TubeHeadType:
			gobj = new TubeHead(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case TubeBodyType:
			gobj = new TubeBody(id, D3DXVECTOR2((float)x,(float)y), movingWidth);
			break;
		case STree:case SFlower: case SCloud:
			gobj = new ScenaryObject(id, D3DXVECTOR2((float)x,(float)y), movingWidth, type);
			break;
		default:
			break;
		}
		if(type!=MarioPlayer)
			mapGameObject.insert(pair<int, GameObject*>(id, gobj));
	}

	bool Check(int id, boolean forDrawing)
	{
		if(forDrawing)
		{
			for(size_t i = 0; i < idListDraw.size(); ++i)
			{
				if(id==idListDraw[i]) return true;
			}
		}
		else
		{
			for(size_t i = 0; i < idListCollision.size(); ++i)
			{
				if(id==idListCollision[i]) return true;
			}
		}

		return false;
	}
};

#endif